package com.swak.vertx.invoker;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeanUtils;

import com.swak.annotation.Body;
import com.swak.annotation.Cache;
import com.swak.annotation.Cookie;
import com.swak.annotation.Header;
import com.swak.annotation.Json;
import com.swak.annotation.Logical;
import com.swak.annotation.RequiresPermissions;
import com.swak.annotation.RequiresRoles;
import com.swak.annotation.Valid;
import com.swak.asm.FieldCache;
import com.swak.asm.FieldCache.ClassMeta;
import com.swak.asm.MethodCache;
import com.swak.asm.MethodCache.MethodMeta;
import com.swak.asm.Wrapper;
import com.swak.entity.UploadFile;
import com.swak.meters.MethodMetrics;
import com.swak.meters.MetricsFactory;
import com.swak.security.Permission;
import com.swak.security.Platform;
import com.swak.security.Subject;
import com.swak.security.permission.AndPermission;
import com.swak.security.permission.OrPermission;
import com.swak.security.permission.SinglePermission;
import com.swak.utils.Lists;
import com.swak.utils.StringUtils;
import com.swak.utils.router.RouterUtils;
import com.swak.validator.errors.BindErrors;
import com.swak.vertx.invoker.mark.CacheMark;
import com.swak.vertx.invoker.mark.HasCacheMark;
import com.swak.vertx.invoker.mark.MoreCacheMark;
import com.swak.vertx.invoker.mark.NoneCacheMark;
import com.swak.vertx.protocol.im.ImContext;
import com.swak.vertx.protocol.im.ImContext.ImRequest;
import com.swak.vertx.protocol.im.ImContext.ImResponse;
import com.swak.vertx.remote.IP;

import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;

/**
 * 基于 method 的执行器
 *
 * @author: lifeng
 * @date: 2020/3/29 20:14
 */
@SuppressWarnings("rawtypes")
public class MethodInvoker {

	private final Class<?> theClass;
	private final Object bean;
	private final Method method;
	private final MethodParameter[] parameters;
	protected Permission requiresRoles;
	protected Permission requiresPermissions;

	/**
	 * 代理此类提高执行效率
	 */
	private Wrapper lazyWrapper;
	protected MethodMeta methodMeta;

	/**
	 * 使用指标统计
	 */
	public MethodMetrics metrics;

	/**
	 * 使用缓存
	 */
	protected CacheMark methodCache;
	protected CacheMark paramCaches;

	public MethodInvoker(Class<?> clazz, Object bean, Method method) {
		this.theClass = clazz;
		this.bean = bean;
		this.method = method;
		// this.wrapper = Wrapper.getWrapper(clazz);
		this.methodMeta = MethodCache.set(clazz).lookup(this.method);
		this.parameters = this.initMethodParameters();
		this.initParameters();
	}

	private MethodParameter[] initMethodParameters() {
		Class<?>[] parameterTypes = this.methodMeta.getParameterTypes();
		Class<?>[] nestedParameterTypes = this.methodMeta.getNestedParameterTypes();
		String[] parameterNames = RouterUtils.getParameterNames(method);
		MethodParameter[] result = new MethodParameter[parameterTypes.length];
		for (int i = 0; i < result.length; i++) {
			result[i] = new MethodParameter(this.method, parameterNames[i], i, parameterTypes[i],
					nestedParameterTypes[i]);
		}
		return result;
	}

	private void initParameters() {
		MethodParameter[] parameters = this.getParameters();
		for (MethodParameter parameter : parameters) {
			// 实际的类型
			Class<?> parameterType = parameter.getNestedParameterType();

			// 对于集合类型支持第一层
			this.initField(parameterType);
		}
	}

	/**
	 * 组装类型，子类型也一并组装
	 */
	private void initField(Class<?> parameterType) {
		if (parameterType == null || HttpServerRequest.class.isAssignableFrom(parameterType)
				|| HttpServerResponse.class.isAssignableFrom(parameterType)
				|| RoutingContext.class.isAssignableFrom(parameterType) || IP.class.isAssignableFrom(parameterType)
				|| ImRequest.class.isAssignableFrom(parameterType) || ImResponse.class.isAssignableFrom(parameterType)
				|| ImContext.class.isAssignableFrom(parameterType) || Subject.class.isAssignableFrom(parameterType)
				|| BindErrors.class.isAssignableFrom(parameterType) || BeanUtils.isSimpleProperty(parameterType)
				|| Collection.class.isAssignableFrom(parameterType) || List.class.isAssignableFrom(parameterType)
				|| Map.class.isAssignableFrom(parameterType) || UploadFile.class.isAssignableFrom(parameterType)
				|| Platform.class.isAssignableFrom(parameterType)) {
			return;
		}

		// 不存在的类型需要去解析, 防止死循环
		if (!FieldCache.exists(parameterType)) {
			// 缓存父类型
			FieldCache.set(parameterType);

			// 子类型
			ClassMeta classMeta = FieldCache.get(parameterType);
			classMeta.getFields().values().forEach(field -> this.initField(field.getNestedFieldClass()));
		}
	}

	public String getMethodAndParametersDesc() {
		MethodParameter[] params = this.getParameters();
		StringBuilder builder = new StringBuilder();
		for (MethodParameter param : params) {
			String className = MethodCache.buildClassName(param.getParameterType());
			builder.append(className).append(" ").append(param.getParameterName())
					.append(MethodCache.PARAM_CLASS_SPLIT);
		}
		if (builder.length() > 0) {
			builder.deleteCharAt(builder.length() - 1);
		}
		return this.methodMeta.getMethodName() + "(" + builder + ")";
	}

	public MethodParameter[] getParameters() {
		return parameters;
	}

	public MethodMeta getMethod() {
		return this.methodMeta;
	}

	public Permission getRequiresRoles() {
		if (this.requiresRoles == null) {
			RequiresRoles roles = (RequiresRoles) this.getAnnotation(RequiresRoles.class);
			if (roles == null || roles.value().length == 0) {
				this.requiresRoles = Permission.NONE;
			} else if (roles.value().length == 1) {
				this.requiresRoles = new SinglePermission(roles.value()[0]);
			} else if (roles.logical() == Logical.AND) {
				this.requiresRoles = new AndPermission(roles.value());
			} else if (roles.logical() == Logical.OR) {
				this.requiresRoles = new OrPermission(roles.value());
			}
		}
		return this.requiresRoles;
	}

	public Permission getRequiresPermissions() {
		if (this.requiresPermissions == null) {
			RequiresPermissions roles = (RequiresPermissions) this.getAnnotation(RequiresPermissions.class);
			if (roles == null || roles.value().length == 0) {
				this.requiresPermissions = Permission.NONE;
			} else if (roles.value().length == 1) {
				this.requiresPermissions = new SinglePermission(roles.value()[0]);
			} else if (roles.logical() == Logical.AND) {
				this.requiresPermissions = new AndPermission(roles.value());
			} else if (roles.logical() == Logical.OR) {
				this.requiresPermissions = new OrPermission(roles.value());
			}
		}
		return this.requiresPermissions;
	}

	public boolean useSecurity() {
		Permission role = this.getRequiresRoles();
		Permission permission = this.getRequiresPermissions();
		return !(role == Permission.NONE && permission == Permission.NONE);
	}

	public CacheMark getMethodCache() {
		if (this.methodCache == null) {
			Cache cache = (Cache) this.getAnnotation(Cache.class);
			if (cache == null || StringUtils.isBlank(cache.name())) {
				this.methodCache = CacheMark.NONE;
			} else {
				this.methodCache = new HasCacheMark(cache);
			}
		}
		return this.methodCache;
	}

	public boolean useMethodCache() {
		CacheMark mark = this.getMethodCache();
		return mark != CacheMark.NONE;
	}

	public CacheMark getParamCaches() {
		if (paramCaches == null) {
			List<CacheMark> marks = null;
			for (MethodParameter param : this.parameters) {
				CacheMark mark = param.getParamCache();
				if (mark != null && mark != NoneCacheMark.NONE) {
					if (marks == null) {
						marks = Lists.newArrayList();
					}
					marks.add(mark);
				}
			}

			if (marks == null || marks.size() <= 0) {
				paramCaches = NoneCacheMark.NONE;
			} else if (marks.size() == 1) {
				paramCaches = marks.get(0);
			} else {
				paramCaches = new MoreCacheMark(marks);
			}
		}
		return paramCaches;
	}

	public boolean useParamCache() {
		CacheMark mark = this.getParamCaches();
		return mark != CacheMark.NONE;
	}

	private <A extends Annotation> Annotation getAnnotation(Class<A> annotationType) {
		Annotation[] paramAnns = this.method.getAnnotations();
		if (paramAnns != null) {
			for (Annotation ann : paramAnns) {
				if (annotationType.isInstance(ann)) {
					return ann;
				}
			}
			return null;
		}
		return null;
	}

	/**
	 * 执行目标方法
	 * 
	 * @param args
	 * @return
	 * @throws Throwable
	 */
	public Object doInvoke(Object[] args) throws Throwable {
		return this.createWrapper().invokeMethod(this.bean, this.methodMeta.getMethodDesc(), args);
	}

	private Wrapper createWrapper() {
		if (lazyWrapper == null) {
			synchronized (this) {
				if (lazyWrapper == null) {
					lazyWrapper = Wrapper.getWrapper(this.theClass);
				}
			}
		}
		return lazyWrapper;
	} 

	/**
	 * 应用指标监控
	 */
	public void applyMetrics(MetricsFactory metricsFactory) {
		String metricName = this.method.getDeclaringClass().getName() + "." + this.methodMeta.getMethodDesc();
		this.metrics = metricsFactory.createMethodMetrics(method, metricName);
	}

	public boolean useMetrics() {
		return this.metrics != null;
	}

	/**
	 * 不支持 Map 的
	 * 
	 * @author lifeng
	 * @date 2020年4月22日 下午8:36:29
	 */
	public class MethodParameter {

		private Method method;
		private int parameterIndex;
		private String parameterName;
		private Class<?> parameterType;
		private Class<?> nestedParameterType;
		private boolean hasInitAnnotation;

		private Body body;
		private Json json;
		private Header header;
		private Cookie cookie;
		private Valid valid;

		/**
		 * 使用缓存
		 */
		public CacheMark paramCache;

		public MethodParameter(Method method, String parameterName, int parameterIndex, Class<?> parameterType,
				Class<?> nestedParameterType) {
			this.method = method;
			this.parameterIndex = parameterIndex;
			this.parameterType = parameterType;
			this.nestedParameterType = nestedParameterType;
			this.parameterName = parameterName;
		}

		public String getParameterName() {
			return parameterName;
		}

		public Class<?> getParameterType() {
			return parameterType;
		}

		public Class<?> getNestedParameterType() {
			return nestedParameterType;
		}

		private void initAnnotations() {
			if (!this.hasInitAnnotation) {
				Annotation[][] annotationArray = this.method.getParameterAnnotations();
				Annotation[] paramAnns = annotationArray[this.parameterIndex];
				if (paramAnns != null) {
					for (Annotation ann : paramAnns) {
						if (Body.class.isInstance(ann)) {
							body = (Body) ann;
						} else if (Json.class.isInstance(ann)) {
							json = (Json) ann;
						} else if (Header.class.isInstance(ann)) {
							header = (Header) ann;
						} else if (Cookie.class.isInstance(ann)) {
							cookie = (Cookie) ann;
						} else if (Valid.class.isInstance(ann)) {
							valid = (Valid) ann;
						} else if (Cache.class.isInstance(ann)) {
							Cache useCache = (Cache) ann;
							if (StringUtils.isNotBlank(useCache.name()) || useCache.paramValueForName() >= 0) {
								this.paramCache = new HasCacheMark(useCache, this.parameterIndex);
							}
						}
					}
				}
				this.hasInitAnnotation = true;
			}
		}

		public Method getMethod() {
			return method;
		}

		public int getParameterIndex() {
			return parameterIndex;
		}

		public boolean hasConvertAnnotation() {
			this.initAnnotations();
			return this.body != null || this.json != null || this.header != null || this.cookie != null;
		}

		public Body getBodyAnnotation() {
			return body;
		}

		public Json getJsonAnnotation() {
			return json;
		}

		public Header getHeaderAnnotation() {
			return header;
		}

		public Cookie getCookieAnnotation() {
			return cookie;
		}

		public Valid getValidAnnotation() {
			return valid;
		}

		public CacheMark getParamCache() {
			if (this.paramCache == null) {
				this.initAnnotations();
				if (this.paramCache == null) {
					this.paramCache = CacheMark.NONE;
				}
			}
			return this.paramCache;
		}
	}
}